#include "OgreAMLPrecompiledHeader.h"

#include "OgreMeshDefinitionManager.h"
#include "OgreMeshDefinition.h"
#include <OgreException.h>
#include <OgreStringConverter.h>
#include <OgreLogManager.h>
#include <OgreString.h>

//-------------------------------------------------------------------------------------------------------------------------------------------------
#if OGRE_COMPILER == OGRE_COMPILER_MSVC
template<> Ogre::MeshDefinitionManager* Ogre::Singleton<Ogre::MeshDefinitionManager>::ms_Singleton = 0;
#else
template<> _OgreAMLExport Ogre::MeshDefinitionManager* Ogre::Singleton<Ogre::MeshDefinitionManager>::ms_Singleton = 0;
#endif // end of #if OGRE_COMPILER == OGRE_COMPILER_MSVC
//-------------------------------------------------------------------------------------------------------------------------------------------------


namespace Ogre
{

//-------------------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------------------
MeshDefinitionManager::ScriptParserCookie::ScriptParserCookie()
: lineNum(0)
{
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------------------
MeshDefinitionManager::MeshDefinitionManager()
{
	// Loading order (just before MeshManager)
	mLoadOrder = 349.0f;
	
	// Scripting is supported by this manager
	mScriptPatterns.push_back("*.meshdef");
	// Register scripting with resource group manager
	ResourceGroupManager::getSingleton()._registerScriptLoader(this);

	// Resource type
	mResourceType = "MeshScript";
	
	// Register with resource group manager
	ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this);
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
MeshDefinitionManager::~MeshDefinitionManager()
{
	// Resources cleared by superclass
	// Unregister with resource group manager
	ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType);
	ResourceGroupManager::getSingleton()._unregisterScriptLoader(this);
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::_initialise()
{
	// nothing to do
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::_shutdown()
{
	// nothing to do
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
Resource* MeshDefinitionManager::createImpl( const String& name, ResourceHandle handle,
											const String& group, bool isManual, ManualResourceLoader* loader,
											const NameValuePairList* createParams )
{
	return OGRE_NEW MeshDefinition( this, name, handle, group, isManual, loader );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseScript( DataStreamPtr& stream, const String& groupName )
{
	// Set a cookie
	ScriptParserCookie cookie;
	cookie.stream = stream;
	cookie.groupName = groupName;

	try
	{
		// Read script
		while( readSingleLine( cookie ) )
		{
			// Parse one line
			parseSingleLine( cookie );
		}
		
		if( !cookie.resource.isNull() )
		{
			OGRE_EXCEPT( Exception::ERR_INVALIDPARAMS,
				"MeshDefinition script fatal error. " + cookie.stream->getName() +
				"(" + StringConverter::toString( cookie.lineNum ) + ")"
				": End of file found before the right brace '}', check script syntax.",
				"MeshDefinitionManager::parseScript" );
		}
	}
	catch( const std::exception& )
	{
		// nothing to do
		return;
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
bool MeshDefinitionManager::readSingleLine( ScriptParserCookie& cookie )
{
	bool skipline = false;
	do
	{
		// Read one line from script
		if( !readSingleLineImpl( cookie ) )
			return false;

		// Ignore blanks & comments
		skipline = (cookie.line.empty() || cookie.line.substr( 0, 2 ) == "//");
	}
	while( skipline );

	return true;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
bool MeshDefinitionManager::readSingleLineImpl( ScriptParserCookie& cookie )
{
	// Is end of file?
	if( cookie.stream->eof() )
		return false;

	// Read one line from script
	cookie.line = cookie.stream->getLine();
	cookie.lineNum++;
	
	// Removes any whitespace characters
	StringUtil::trim( cookie.line );

	return true;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseSingleLine( ScriptParserCookie& cookie )
{
	// Argument name and value
	String name, value;

	// Look for argument name/value delimiter
	String::size_type delimiterIndex = cookie.line.find( " " );
	if( delimiterIndex == String::npos )
	{
		// Argument value is empty
		name = cookie.line;
	}
	else
	{
		// Retrieve agrument name
		name = cookie.line.substr( 0, delimiterIndex );
		// Retrieve agrument value
		value = cookie.line.substr( delimiterIndex, String::npos );
		// Removes any whitespace characters from value
		StringUtil::trim( value );
	}

	// Has resource
	if( cookie.resource.isNull() )
	{
		// No current resource
		// So first valid data should be mesh name
		if( name != "mesh" )			
		{
			OGRE_EXCEPT( Exception::ERR_INVALIDPARAMS,
			"MeshDefinition script fatal error. " + cookie.stream->getName() +
				"(" + StringConverter::toString( cookie.lineNum ) + ")"
				": First valid data should be mesh name (mesh <name>), check script syntax.",
				"MeshDefinitionManager::parseSingleLine" );
		}

		// Create new MeshDefinition resource
		cookie.resource = create( value, cookie.groupName );
		cookie.resource->_notifyOrigin( cookie.stream->getName() );
		
		// Skip to and over next '{'
		cookie.stream->skipLine( "{" );
		return;
	}

	// Parsing script attribute
	parseAttribute( cookie, name, value );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseAttribute( ScriptParserCookie& cookie, const String& name, const String& value )
{
	if( name == "}" )
	{
		// Just touch mesh def and prepare 'cookie' for next resource
		parseRightBraceAttribute( cookie, value );
	}
	else if( name == "mesh" )
	{
		OGRE_EXCEPT( Exception::ERR_INVALIDPARAMS,
			"MeshDefinition script fatal error. " + cookie.stream->getName() +
			"(" + StringConverter::toString( cookie.lineNum ) + ")"
			": Expected '}' before new mesh, check script syntax.",
			"MeshDefinitionManager::parseAttribute" );
	}
	else if( name == "file" )
	{
		// Set file name for mesh
		parseFileAttribute( cookie, value );
	}
	else if( name == "lod" )
	{
		// Parse manual lod settings
		parseLodAttribute( cookie, value );
	}
	else if( name == "lod_method" )
	{
		// Parsing lod method (manual|progressive)
		parseLodMethodAttribute( cookie, value );
	}
	else if( name == "progressive_reduction_method" )
	{
		// Parsing reduction method (constant|proportional)
		parseProgressiveReductionMethodAttribute( cookie, value );
	}
	else if( name == "reduction" )
	{
		// Parsing reduction value
		parseReductionAttribute( cookie, value );
	}
	else if( name == "progressive_lod_values" )
	{
		// Parsing lod values
		parseProgressiveLodValuesAttribute( cookie, value );
	}
	else if( name == "preload_lods" )
	{
		// Parsing preload lods value
		parsePreloadLobdsAttribute( cookie, value );
	}
	else
	{
		// Log error on unknown attribute
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Unknown attribute '" << name <<"'.";
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseRightBraceAttribute( ScriptParserCookie& cookie, const String& value )
{
	// 'Touch' the mesh def
	cookie.resource->touch();

	// Clear resource
	cookie.resource.setNull();
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseFileAttribute( ScriptParserCookie& cookie, const String& value )
{
	// Is value empty?
	if( value.empty() )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Attribute 'file' is empty.";
	}

	// Set real mesh file name
	cookie.resource->setMeshFileName( value );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseLodAttribute( ScriptParserCookie& cookie, const String& value )
{
	// Lod value and mesh name
	String lodValue, meshName;

	// Look for lod value/mesh name
	String::size_type index = value.find( " " );
	if( index == String::npos )
	{
		// Argument value is empty
		lodValue = value;
	}
	else
	{
		// Retrieve agrument name
		lodValue = value.substr( 0, index );
		// Retrieve agrument value
		meshName = value.substr( index, String::npos );
		// Removes any whitespace characters from meshName
		StringUtil::trim( meshName );
	}

	// Is lod value number?
	if( !StringConverter::isNumber( lodValue ) )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Lod value should be a number.";
		return;
	}

	// Is mesh name empty?
	if( meshName.empty() )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Mesh name is empty.";
		return;
	}

	// Add lod level
	cookie.resource->addLodLevel( StringConverter::parseReal( lodValue ), meshName );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseLodMethodAttribute( ScriptParserCookie& cookie, const String& value )
{
	// Is value empty?
	if( value.empty() )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Attribute 'lod_method' is empty.";
		return;
	}

	// Set lod method
	if( value == "manual" )
	{
		cookie.resource->setLodMethod( MeshDefinition::LM_MANUAL );
	}
	else if( value == "progressive" )
	{
		cookie.resource->setLodMethod( MeshDefinition::LM_PROGRESSIVE );
	}
	else
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Invalid value for attribute 'lod_method'.";
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseProgressiveReductionMethodAttribute( ScriptParserCookie& cookie, const String& value )
{
	// Is value empty?
	if( value.empty() )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Attribute 'progressive_reduction_method' is empty.";
		return;
	}

	// Set lod method
	if( value == "constant" )
	{
		cookie.resource->setProgressiveMeshReductionMethod( ProgressiveMesh::VRQ_CONSTANT );
	}
	else if( value == "proportional" )
	{
		cookie.resource->setProgressiveMeshReductionMethod( ProgressiveMesh::VRQ_PROPORTIONAL );
	}
	else
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Invalid value for attribute 'progressive_reduction_method'.";
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseReductionAttribute( ScriptParserCookie& cookie, const String& value )
{
	// Is value empty?
	if( value.empty() )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Attribute 'reduction' is empty.";
		return;
	}

	// Is reduction value number?
	if( !StringConverter::isNumber( value ) )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Reduction value should be a number.";
		return;
	}

	// Set reduction value
	cookie.resource->setReductionValue( StringConverter::parseReal( value ) );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parseProgressiveLodValuesAttribute( ScriptParserCookie& cookie, const String& value )
{
	// Is value empty?
	if( value.empty() )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Attribute 'progressive_lod_values' is empty.";
		return;
	}
	
	// Split value 
	vector< String >::type strlist = StringUtil::tokenise( value, ", " );
	Mesh::LodValueList lodValues;

	// Itarate all values
	for( vector< String >::type::iterator it = strlist.begin();
		it != strlist.end(); it++ )
	{
		// Is value number?
		if( !StringConverter::isNumber( *it ) )
		{
			LogManager::getSingleton().stream() <<
				"MeshDefinition script error. " << cookie.stream->getName() <<
				"(" << StringConverter::toString( cookie.lineNum ) << "): Invalid lod value '" << *it << "'. "
				"Lod value should be a number.";

			continue;
		}

		// Add value to list
		lodValues.push_back( StringConverter::parseReal( *it ) );
	}

	// Is lod values list empty?
	if( lodValues.empty() )
		return;

	cookie.resource->setProgressiveLodValueList( lodValues );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void MeshDefinitionManager::parsePreloadLobdsAttribute( ScriptParserCookie& cookie, const String& value )
{
	// Is value empty?
	if( value.empty() )
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Attribute 'preload_lods' is empty.";
		return;
	}

	// Set lod method
	if( value == "yes" )
	{
		cookie.resource->setPreloadLodMeshes( true );
	}
	else if( value == "no" )
	{
		cookie.resource->setPreloadLodMeshes( false );
	}
	else
	{
		LogManager::getSingleton().stream() <<
			"MeshDefinition script error. " << cookie.stream->getName() <<
			"(" << StringConverter::toString( cookie.lineNum ) << "): Invalid value for attribute 'preload_lods'.";
	}
}

} // end of namespace Ogre
